/* Depends: InitClass.js, Descriptorjs, Control.js
 *
 */

NJS.InlineLayer = NJS.Control.extend({
    CLASS_SIGNATURE: "NJS_InlineLayer",
    SCROLL_HIDE_EVENT: "scroll.hide-dropdown",
    init: function (options) {
        var instance = this;
		// If the options is not NJS.InlineLayer.OptionsDescriptor
		// create a new NJS.InlineLayer.OptionsDescriptor
		// else just assign to this.options
        if (!(options instanceof NJS.InlineLayer.OptionsDescriptor)) {
            this.options = new NJS.InlineLayer.OptionsDescriptor(options)
        } else {
            this.options = options
        }
        this.offsetTarget(this.options.offsetTarget());
        this.contentRetriever = this.options.contentRetriever();
        this.positionController = this.options.positioningController();
        if (!(this.contentRetriever instanceof NJS.ContentRetriever)) {
            throw new Error("NJS.InlineLayer: Failed construction, Content retriever does not implement interface " + "[NJS.ContentRetrieverInterface]")
        }
        this.contentRetriever.startingRequest(function () {
            instance._showLoading()
        });
        this.contentRetriever.finishedRequest(function () {
            instance._hideLoading()
        });
        this.$layer = this._render("layer", this.options.alignment())
    },
    content: function (arg) {
        var instance = this;
        if ($.isFunction(arg)) {
            if (this.contentRetriever.isLocked()) {
                throw new Error(this.CLASS_SIGNATURE + ".content() : Illegal operation, trying to access content while it is " + "locked. If you are seeing this error it is most likely because we are waiting for an request to " + "come back from the server that builds content")
            }
            this.contentRetriever.content(function (content) {
                instance.$content = content.removeClass("hidden");
                arg.call(instance)
            })
        } else {
            return this.$content
        }
    },
    offsetTarget: function (offsetTarget) {
        if (offsetTarget) {
            this.$offsetTarget = $(offsetTarget)
        }
        return this.$offsetTarget
    },
    contentChange: function (callback) {
        var event, instance = this;
        if ($.isFunction(callback)) {
            if (!this.contentChangeCallback) {
                this.contentChangeCallback = []
            }
            this.contentChangeCallback.push(callback)
        } else {
            if (!callback && this.contentChangeCallback) {
                $.each(this.contentChangeCallback, function (i, callback) {
                    callback(instance)
                });
                NJS.trigger("contentChange", this.layer());
                this.setWidth(this.options.width())
            }
        }
    },
    onhide: function (callback) {
        var instance = this;
        if ($.isFunction(callback)) {
            if (!this.hideCallback) {
                this.hideCallback = []
            }
            this.hideCallback.push(callback)
        } else {
            if (!callback && this.hideCallback) {
                $.each(this.hideCallback, function (i, callback) {
                    callback(instance)
                })
            }
        }
    },
    layer: function (layer) {
        if (layer) {
            this.$layer = layer
        } else {
            return this.$layer
        }
    },
    placeholder: function (placeholder) {
        if (placeholder) {
            this._throwReadOnlyError("placeholder")
        } else {
            return this.$placeholder
        }
    },
    isVisible: function (visible) {
        if (typeof visible !== "undefined") {
            this._throwReadOnlyError("visible")
        }
        return this.visible
    },
    scrollableContainer: function (scrollableContainer) {
        if (scrollableContainer) {
            this._throwReadOnlyError("scrollableContainer")
        }
        return this.$scrollableContainer
    },
    isInitialized: function (initialised) {
        if (initialised) {
            this._throwReadOnlyError("initialized")
        }
        return this.initialized
    },
    hide: function () {
        if (!this.isVisible()) {
            return false
        }
        this.visible = false;
        this.layer().removeClass(NJS.ACTIVE_CLASS).hide();
        this.$offsetTarget.removeClass(NJS.ACTIVE_CLASS);
        var positionController = this.positionController;
        setTimeout(function () {
            positionController.appendToPlaceholder()
        }, 0);
        this._unbindEvents();
        this.onhide();
        $(document).trigger("hideLayer", [this.CLASS_SIGNATURE, this]);
        NJS.InlineLayer.current = null
    },
    refreshContent: function (callback) {
        var instance = this;
        this.content(function () {
            this.layer().empty().append(this.content());
            if ($.isFunction(callback)) {
                callback.call(instance)
            }
            this.contentChange()
        })
    },
    show: function () {
        var instance = this;
        if (this.isVisible()) {
            return
        }
        if (!this.isInitialized()) {
            this._lazyInit(function () {
                instance._show()
            })
        } else {
            if (this.contentRetriever.cache() === false) {
                this.refreshContent(function () {
                    instance._show()
                })
            } else {
                instance._show()
            }
        }
    },
    setPosition: function () {
        var positioning, scrollTop;
        if (!this.isInitialized() || !this.offsetTarget() || this.offsetTarget().length === 0) {
            return
        }
        if (this.options.alignment() === NJS.RIGHT) {
            positioning = this.positionController.right()
        } else {
            if (this.options.alignment() === NJS.LEFT) {
                positioning = this.positionController.left()
            } else {
                if (this.options.alignment() === NJS.INTELLIGENT_GUESS) {
                    if ((this.offsetTarget().offset().left + this.layer().outerWidth() / 2) > $(window).width() / 2) {
                        positioning = this.positionController.right()
                    } else {
                        positioning = this.positionController.left()
                    }
                }
            }
        }
        if (NJS.dim.dim) {
            scrollTop = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
            positioning.maxHeight = $(window).height() + scrollTop - positioning.top - this.options.cushion()
        }
        this.layer().css(positioning)
    },
    setWidth: function (width, showhorizontalScroll) {
        var contentScrollWidth = this.content().attr("scrollWidth");
        if (!(this.content().hasClass("error") || this.content().hasClass("warn"))) {
            this.content().css({
                whiteSpace: "nowrap",
                overflowX: "",
                width: "auto"
            })
        }
        if (contentScrollWidth <= width) {
            this.layer().css({
                width: width,
                whiteSpace: ""
            })
        } else {
            if (showhorizontalScroll) {
                this.layer().css({
                    width: width,
                    overflowX: "auto",
                    whiteSpace: ""
                })
            } else {
                this.layer().css({
                    width: contentScrollWidth,
                    overflowX: "hidden",
                    whiteSpace: ""
                })
            }
        }
    },
    _lazyInit: function (callback) {
        this.initialized = true;
        this.refreshContent(function () {
            var instance = this;
            this.layer().insertAfter(this.offsetTarget());
            if (this._supportsBoxShadow()) {
                this.layer().addClass(NJS.BOX_SHADOW_CLASS)
            }
            this.$placeholder = $("<div class='NJS-layer-placeholder' />").insertAfter(this.offsetTarget());
            this.$scrollableContainer = this.offsetTarget().closest(this.options.hideOnScroll());
            this.positionController.set(this.layer(), this.offsetTarget(), this.placeholder());
            this.positionController.rebuilt(function (layer) {
                instance.layer(layer)
            });
            callback()
        })
    },
    _show: function () {
        if (NJS.InlineLayer.current) {
            NJS.InlineLayer.current.hide()
        }
        this.visible = true;
        this.content().show();
        this.positionController.appendToBody();
        this.layer().addClass(NJS.ACTIVE_CLASS);
        this.$offsetTarget.addClass(NJS.ACTIVE_CLASS);
        this.layer().show();
        this.setWidth(this.options.width());
        this.setPosition();
        this._bindEvents();
        if (!NJS.dim.dim) {
            this.positionController.scrollTo()
        }
        NJS.InlineLayer.current = this;
        $(document).trigger("showLayer", [this.CLASS_SIGNATURE, this])
    },
    _hideLoading: function () {
        this.$layer.removeClass(NJS.LOADING_CLASS);
        this.$offsetTarget.removeClass(NJS.LOADING_CLASS)
    },
    _showLoading: function () {
        this.$layer.addClass(NJS.LOADING_CLASS);
        this.$offsetTarget.addClass(NJS.LOADING_CLASS)
    },
    _unbindEvents: function () {
        this.$scrollableContainer.unbind(this.SCROLL_HIDE_EVENT);
        this._unassignEvents("container", document);
        this._unassignEvents("win", window)
    },
    _bindEvents: function () {
        var instance = this;
        this.$scrollableContainer.one(this.SCROLL_HIDE_EVENT, function () {
            instance.hide()
        });
        this._assignEvents("container", document);
        this._assignEvents("win", window)
    },
    _validateClickToClose: function (e) {
        if (e.target === this.offsetTarget()[0]) {
            return false
        } else {
            if (e.target === this.layer()[0]) {
                return false
            } else {
                if (this.offsetTarget().has(e.target).length > 0) {
                    return false
                } else {
                    if (this.layer().has(e.target).length > 0) {
                        return false
                    }
                }
            }
        }
        return true
    },
    _events: {
        container: {
            keydown: function (e) {
                if (JIRA.Keyboard.specialKeyEntered(e) === JIRA.Keyboard.SpecialKey.ESC) {
                    this.hide()
                }
            },
            keypress: function (e) {
                if (JIRA.Keyboard.specialKeyEntered(e) === JIRA.Keyboard.SpecialKey.ESC) {
                    this.hide()
                }
            },
            click: function (e) {
                if (this._validateClickToClose(e)) {
                    this.hide()
                }
            }
        },
        win: {
            resize: function () {
                this.setPosition()
            },
            scroll: function () {
                this.setPosition()
            }
        }
    },
    _renders: {
        layer: function () {
            return $("<div />").addClass("NJS-layer " + (this.options.styleClass() || ""))
        }
    }
});

NJS.InlineLayer.OptionsDescriptor = NJS.Descriptor.extend({
    init: function (properties) {
        this._super(properties);
        if (!this.contentRetriever()) {
            if (this.ajaxOptions()) {
                this.contentRetriever(new NJS.AjaxContentRetriever(this.ajaxOptions()))
            } else {
                if (this.content()) {
                    this.contentRetriever(new NJS.DOMContentRetriever(this.content()))
                } else {
                    throw new Error("NJS.InlineLayer.OptionsDescriptor: Expected either [ajaxOptions] or [contentRetriever] or [content] to be defined")
                }
            }
        }
        if (!NJS.params.ignoreFrame && this._inIFrame()) {
            this.positioningController(new NJS.InlineLayer.IframePositioning())
        } else {
            this.positioningController(new NJS.InlineLayer.StandardPositioning())
        }
        if ($.browser.msie && this.positioningController().isOffsetIncludingScroll) {
            this.positioningController().isOffsetIncludingScroll(false)
        }
        if (!this.offsetTarget() && this.content()) {
            this.offsetTarget(this.content().prev())
        }
    },
    _inIFrame: function () {
        var parentWindow = window;
        try {
            while (parentWindow.parent.window !== parentWindow.window) {
                parentWindow = parentWindow.parent;
                if (parentWindow.NJS) {
                    return true
                }
            }
        } catch (error) {}
        return false
    },
    _getDefaultOptions: function () {
        return {
            alignment: NJS.INTELLIGENT_GUESS,
            hideOnScroll: ".content-body",
            cushion: 20,
            width: 200
        }
    },
    positioningController: function (positioningController) {
        if (positioningController) {
            this.properties.positioningController = positioningController
        } else {
            return this.properties.positioningController
        }
    },
    ajaxOptions: function (ajaxOptions) {
        if (ajaxOptions) {
            this.properties.ajaxOptions = ajaxOptions
        } else {
            return this.properties.ajaxOptions
        }
    },
    content: function (content) {
        if (content) {
            content = $(content);
            if (content.length) {
                this.properties.content = content
            }
        } else {
            if (this.properties.content && this.properties.content.length) {
                return this.properties.content
            }
        }
    },
    contentRetriever: function (contentRetriever) {
        if (contentRetriever) {
            this.properties.contentRetriever = contentRetriever
        } else {
            return this.properties.contentRetriever
        }
    },
    offsetTarget: function (offsetTarget) {
        if (offsetTarget) {
            offsetTarget = $(offsetTarget);
            if (offsetTarget.length) {
                this.properties.offsetTarget = offsetTarget
            }
        } else {
            if (this.properties.offsetTarget && this.properties.offsetTarget.length) {
                return this.properties.offsetTarget
            }
        }
    },
    cushion: function (cushion) {
        if (cushion) {
            this.properties.cushion = cushion
        } else {
            return this.properties.cushion
        }
    },
    styleClass: function (styleClass) {
        if (styleClass) {
            this.properties.styleClass = styleClass
        } else {
            return this.properties.styleClass
        }
    },
    hideOnScroll: function (hideOnScroll) {
        if (hideOnScroll) {
            this.properties.hideOnScroll = hideOnScroll
        } else {
            return this.properties.hideOnScroll
        }
    },
    alignment: function (alignment) {
        if (alignment) {
            this.properties.alignment = alignment
        } else {
            return this.properties.alignment
        }
    },
    width: function (width) {
        if (width) {
            this.properties.width = width
        } else {
            return this.properties.width
        }
    }
});
NJS.InlineLayer.create = function (options) {
    var inlineLayers = [];
    if (options.content) {
        options.content = $(options.content);
        $.each(options.content, function () {
            var instanceOptions = NJS.copyObject(options);
            instanceOptions.content = $(this);
            inlineLayers.push(new NJS.InlineLayer(instanceOptions))
        })
    }
    if (inlineLayers.length == 1) {
        return inlineLayers[0]
    } else {
        return inlineLayers
    }
};
NJS.InlineLayer.StandardPositioning = Class.extend({
    set: function ($layer, $offsetTarget, $placeholder) {
        this.$layer = $layer;
        this.$offsetTarget = $offsetTarget;
        this.$placeholder = $placeholder;
        this.rebuiltCallbacks = []
    },
    left: function () {
        var offset = this.offset();
        return {
            left: offset.left,
            top: offset.top + this.$offsetTarget.outerHeight()
        }
    },
    right: function () {
        var offset = this.offset();
        return {
            left: this.offset().left - this.$layer.outerWidth() + this.$offsetTarget.outerWidth(),
            top: offset.top + this.$offsetTarget.outerHeight()
        }
    },
    window: function () {
        return window
    },
    offset: function () {
        var offset = this.$offsetTarget.offset();
        if (this.$offsetTarget.hasFixedParent()) {
            this.$layer.css("position", "fixed");
            offset.top = offset.top - $(window).scrollTop()
        } else {
            this.$layer.css("position", "absolute")
        }
        return offset
    },
    rebuilt: function (arg) {
        var instance = this;
        if ($.isFunction(arg)) {
            this.rebuiltCallbacks.push(arg)
        } else {
            $.each(this.rebuiltCallbacks, function () {
                this(instance.$layer)
            })
        }
    },
    appendToBody: function () {
        this.$layer.appendTo("body")
    },
    appendToPlaceholder: function () {
        this.$layer.appendTo(this.$placeholder)
    },
    scrollTo: function () {}
});
NJS.InlineLayer.IframePositioning = NJS.InlineLayer.StandardPositioning.extend({
    offset: function () {
        var offsetInDocument = this._super(),
            iframeOffset = $("iframe[name=" + window.name + "]", window.top.document.body).parent().offset(),
            topDocumentScrollTop = this._topDocumentScrollTop(),
            topDocumentScrollLeft = this._topDocumentScrollLeft(),
            iframeScrollTop = this._iframeScrollTop(),
            iframeScrollLeft = this._iframeScrollLeft(),
            scrollTopModifier = topDocumentScrollTop - iframeScrollTop,
            scrollLeftModifier = topDocumentScrollLeft - iframeScrollLeft;
        return {
            left: iframeOffset.left + offsetInDocument.left + scrollLeftModifier,
            top: iframeOffset.top + offsetInDocument.top + scrollTopModifier
        }
    },
    _topDocumentScrollTop: function () {
        return this.isOffsetIncludingScroll() ? 0 : Math.max(window.top.document.body.scrollTop, window.top.document.documentElement.scrollTop)
    },
    _topDocumentScrollLeft: function () {
        return this.isOffsetIncludingScroll() ? 0 : Math.max(window.top.document.body.scrollLeft, window.top.document.documentElement.scrollLeft)
    },
    _iframeScrollTop: function () {
        return this.isOffsetIncludingScroll() ? 2 * Math.max(window.document.body.scrollTop, window.document.documentElement.scrollTop) : 0
    },
    _iframeScrollLeft: function () {
        return this.isOffsetIncludingScroll() ? 2 * Math.max(window.document.body.scrollLeft, window.document.documentElement.scrollLeft) : 0
    },
    isOffsetIncludingScroll: function (offsetIncludingScroll) {
        if (typeof this.offsetIncludingScroll === "undefined") {
            this.offsetIncludingScroll = true
        }
        if (typeof offsetIncludingScroll !== "undefined") {
            this.offsetIncludingScroll = offsetIncludingScroll
        }
        return this.offsetIncludingScroll
    },
    appendToBody: function () {
        window.top.jQuery("body").append(this.$layer)
    },
    window: function () {
        return window.top
    },
    scrollTo: function () {}
});
if ($.browser.safari || ($.browser.msie && $.browser.version < 8)) {
    NJS.InlineLayer.IframePositioning = NJS.InlineLayer.IframePositioning.extend({
        appendToBody: function () {
            this.$layer = this._rebuildLayerInParent();
            window.top.jQuery("body").append(this.$layer);
            this.rebuilt()
        },
        appendToPlaceholder: function () {
            this.$layer = this._rebuildLayerInIframe();
            this.$layer.appendTo(this.$placeholder);
            this.rebuilt()
        },
        _rebuildLayerInParent: function () {
            return window.top.jQuery("<div class='NJS-layer'>" + this.$layer.html() + "</div>")
        },
        _rebuildLayerInIframe: function () {
            return $("<div class='NJS-layer'>" + this.$layer.html() + "</div>")
        }
    })
};
